/* calc
Michael Zahniser, 09/21/09

Command line program that can take a table of numbers in plain text and 
calculate various summaries of the columns.

Supported formats:
Tab delimited
Space delimited
Comma delimited
Header line, or no headers.
\r or \n line separators.

Supported operations:
sum
mean
sigma
cv
median
min
max
*/

#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <algorithm>
#include <numeric>
#include <cmath>
#include <limits>

using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::list;
using std::vector;
using std::map;
using std::sort;
using std::accumulate;
using std::sqrt;
using std::numeric_limits;



// Helper functions:
// Return true if *it is a substring of target.
bool Matches(const char *it, const string &target);
// Read a value from standard in.
// Return NaN if the end of line or end of input is reached.
double Read();
// Check if the given character is numeric.
bool IsNumeric(char c);

class Stats : public map<string, double> {
public:
	Stats(vector<double> &data);
	
private:
	struct SqSum {
		double operator()(double acc, double add) { return acc + add * add; }
	};
};



int main(int argc, char *argv[])
{
	// Parse the command line arguments to figure out what operations to print
	// the results of. If there are no command line arguments, we'll print all.
	// These are the permitted operations, in the order we should print them.
	static const string OPS[] = {
		"count", "sum", "sqsum", 
		"min", "median", "mean", "rms", "max", 
		"variance", "sigma", "cv"};
	// This array stores which operations have been requested.
	static const int OP_COUNT = sizeof(OPS) / sizeof(string);
	bool doOp[OP_COUNT];
	
	// If there are no arguments, calculate all features.
	// Otherwise, only calculate the named features.
	for(int op = 0; op < OP_COUNT; ++op)
		doOp[op] = (argc <= 1);
	
	// Check the command line arguments.
	for(char **it = argv + 1; *it; ++it)
		for(int op = 0; op < OP_COUNT; ++op)
			doOp[op] |= Matches(*it, OPS[op]);

	// Read in the data.
	list< vector<double> > data;
	list< vector<double> >::iterator lit = data.begin();
	while(cin)
	{
		double value = Read();
		
		// If the value is a valid number, add it to the list.
		if(value == value)
		{
			if(lit == data.end())
			{
				// Create a new column if necessary.
				data.push_back(vector<double>());
				lit = data.end();
				--lit;
			}
			
			lit->push_back(value);
			++lit;
		}
		else
			lit = data.begin();
	}
	
	if(data.empty())
		return 0;
	
	// Perform the different caclulations. Store the features here:
	list<Stats> feat;
	for(lit = data.begin(); lit != data.end(); ++lit)
		feat.push_back(Stats(*lit));
	
	// Print the specified measurements.
	for(int op = 0; op < OP_COUNT; ++op)
		if(doOp[op])
		{
			cout << OPS[op] << ':';
			for(list<Stats>::iterator fit = feat.begin(); fit != feat.end(); ++fit)
				cout << '\t' << (*fit)[OPS[op]];
			cout << endl;
		}

	return 0;
}



// Return true if *it is a substring of target.
bool Matches(const char *it, const string &target)
{
	for(unsigned i = 0; i < target.length() && *it == target[i]; ++i, ++it) {}
	
	// It's a match if the first point where they don't match is when we
	// reach the end of the input string.
	return (*it == '\0');
}



// Read a value from standard in.
// Return NaN if the end of line or end of input is reached.
double Read()
{
	static const double nan = numeric_limits<double>::quiet_NaN();
	
	char next = '\0';
	while(cin && !IsNumeric(next = cin.peek()))
	{
		cin.get();
		if(next == '\r' || next == '\n')
			return nan;
	}
	
	if(!cin)
		return nan;
	
	unsigned long long value = 0;
	unsigned long long place = 1;
	int shift = 1;
	bool isNegative = false;
	
	do {
		cin.get();
		if(next == '.')
			shift = 10;
		else if(next == '-')
			isNegative = true;
		else
		{
			value *= 10;
			place *= shift;
			value += (next - '0');
		}
	} while(cin && IsNumeric(next = cin.peek()));
	
	double result = static_cast<double>(value) / static_cast<double>(place);
	return isNegative ? -result : result;
}



// Check if the given character is numeric.
bool IsNumeric(char c)
{
	return (c >= '0' && c <= '9') || (c == '-') || (c == '.');
}



Stats::Stats(vector<double> &data)
{
	sort(data.begin(), data.end());
	
	(*this)["count"] = static_cast<double>(data.size());
	
	(*this)["sum"] = accumulate(data.begin(), data.end(), 0.);
		
	(*this)["mean"] = (*this)["sum"] / (*this)["count"];
	
	(*this)["min"] = data.front();
	
	size_t middle = data.size() / 2;
	(*this)["median"] = (data.size() & 1) ?
		data[middle] :
		(data[middle - 1] + data[middle]) * .5;
	
	(*this)["max"] = data.back();
	
	SqSum sqsum;
	(*this)["sqsum"] = accumulate(data.begin(), data.end(), 0., sqsum);
	
	(*this)["rms"] = sqrt((*this)["sqsum"] / (*this)["count"]);
	
	(*this)["variance"] = ((*this)["sqsum"] - 
		(*this)["sum"] * (*this)["mean"]) / (*this)["count"];
	
	(*this)["sigma"] = sqrt((*this)["variance"]);
	
	(*this)["cv"] = (*this)["sigma"] / (*this)["mean"];
}
